CloudWatch Alarm から通知される文章の UTC時刻を JST時刻に差し替えたい

CloudWatch Alarm から通知される文章の UTC時刻を JST時刻に差し替えたい

CloudWatch Alarm からのSNS通知を一旦 Lambdaに噛ませます。LambdaでJST情報を付与して再度 SNS通知します。
Clock Icon2020.09.16

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

img

上図のように CloudWatch Alarmから 通知先にSNSトピックを選択したときに送られるメールは以下のような文章です。

   
You are receiving this email because your Amazon CloudWatch Alarm "alarm-test-ec2-cpu" in the Asia Pacific (Tokyo) region has entered the ALARM state, because "Threshold Crossed: 1 out of the last 1 datapoints [50.0 (15/09/20 06:51:00)] was greater than the threshold (70.0) (minimum 1 datapoint for OK -> ALARM transition)." at "Tuesday 15 September, 2020 06:56:39 UTC".

View this alarm in the AWS Management Console:
(CloudWatch Alarmへのリンク)

Alarm Details:
- Name: alarm-test-ec2-cpu
- Description:  XXX
- State Change: OK -> ALARM
- Reason for State Change: Threshold Crossed: 1 out of the last 1 datapoints [50.0 (15/09/20 06:51:00)] was greater than the threshold (70.0) (minimum 1 datapoint for OK -> ALARM transition).
- **Timestamp:**  Tuesday 15 September, 2020 06:56:39 UTC
- AWS Account: XXXXXXXXXXXX
- Alarm Arn:  arn:aws:cloudwatch:ap-northeast-1:XXXXXXXXXXXX:alarm:alarm-test-ec2-cpu

(以下略)

Timestamp のタイムゾーンが UTCなので、 これをローカル(JST)にしたい です。

背景として、CloudWatch Alarm の通知を利用した運用をされている方は多いと思います。 メールが届いたときの、何かしらの初動・調査を行う際に 毎回 時刻の部分が UTCだと、 変換に手間がかかりますよね。

CloudWatchマネジメントコンソール上では ローカルタイムゾーンとして、 様々なグラフ・メトリクスを見ることができます。 しかし、CloudWatch Alarmからの通知文のタイムスタンプを JSTにするオプションはありません。

そこで、今回は Lambdaを介して JSTの時刻を付与したメッセージを送信する構成を作ってみます。 以下のような構成です。

img

構成

作成するリソースは以下の通り。先程の構成図通りです。

  • SNSトピック(via CW Alarm): CloudWatch Alarm の通知用。Lambda関数のトリガー
  • SNSトピック(via Lambda): Lambda関数からの通知用。メール送信
  • Lambda関数: CloudWatch Alarmからのメッセージの処理用

SNSトピックは2つ作成します。CloudWatch Alarm からの通知先と、Lambda関数からの通知先です。 SNSトピック(via Lambda) を購読(subscribe)しておきます。

Lambda関数の実行ロールの権限は以下の通り。 Lambdaの基本的な実行ポリシー ( AWSLambdaBasicExecutionRole )に sns:Publish を追加したものです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "sns:Publish"
            ],
            "Resource": "*",
            "Effect": "Allow"
        }
    ]
}

Lambda Function

Python3.7 で Lambda Functionを作成します。以下のようなコードを書いてみました。 タイムゾーンの変換に pytz を使用しています。

import json
import os
import boto3
from pytz import timezone
from dateutil import parser

SNS_TOPIC_ARN = os.environ['SNS_TOPIC_ARN']
client = boto3.client("sns")


def lambda_handler(event, context):
    subject = event["Records"][0]["Sns"]["Subject"]
    message = json.loads(event["Records"][0]["Sns"]["Message"])
    client.publish(
        TopicArn=SNS_TOPIC_ARN,
        Subject=subject,
        Message=parse(message)
    )


def parse(message):
    texts = []
    # parse UTC to JST
    jst_time = parser.parse(message["StateChangeTime"]
                            ).astimezone(timezone('Asia/Tokyo'))
    # Summary
    texts.append("{} state is now {}: {}".format(
        message["AlarmName"],
        message["NewStateValue"],
        message["NewStateReason"]
    ))
    texts.append("StateChangeTime(JST): {}".format(jst_time))
    # Details
    texts.append("### Details")
    for k, v in message.items():
        texts.append("- {}: {}".format(k, v))
    return "\n".join(texts)

環境変数に SNS_TOPIC_ARN を作成して、 SNSトピック(via Lambda)のARNを指定します。

※SNS通知が Lambdaに渡されるときのイベントフォーマットは以下参考になります。

SAMで構築

上記構成をAWS サーバーレスアプリケーションモデル (AWS SAM)を使って構築しました。 SAMのプロジェクト構成内容を記します。

プロジェクト

sam init で新規プロジェクトを作成します。

sam init --runtime python3.7 --name cw-alarm-notification-with-jst

template.yaml

必要なリソースを記述した template.yaml は以下です。

    
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: change the UTC to JST, for SNS topics via CW Alarm

Parameters:
  EmailAddress:
    Type: String

Resources:
  # SNS topic (used by CW Alarm)
  SnsTopicFromCWAlarm:
    Type: AWS::SNS::Topic
    Properties: 
      TopicName: cw-alarm-topic
  # SNS topic (used by Lambda)
  SnsTopicFromLambda:
    Type: AWS::SNS::Topic
    Properties: 
      TopicName: cw-alarm-topic-jst-processed
  SnsSubscription:
    Type: AWS::SNS::Subscription
    Properties: 
      Protocol: email
      Endpoint: !Ref EmailAddress
      TopicArn: !Ref SnsTopicFromLambda
  # IAM Role
  IamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: "LambdaPolicy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                  - "sns:Publish"
                Resource: "*"
  # Function
  Function:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/
      Handler: app.lambda_handler
      Runtime: python3.7
      Environment:
        Variables:
          SNS_TOPIC_ARN: !Ref SnsTopicFromLambda
      Role: !GetAtt IamRole.Arn
      Events:
        EventBridgeRule:
          Type: SNS
          Properties:
            Topic: !Ref SnsTopicFromCWAlarm

src/app.py (Lambda関数)

前述のLambda関数のコードを src/app.py に格納します。

src/requirements.txt

必要なパッケージを記します。タイムゾーン指定に pytz を使用するため、記載します。

   
pytz

ビルド、デプロイ

sam build
sam deploy --guided

デプロイの設定は下記のとおりです。パラメータに指定した EmailAddress に送信先メールアドレス を記入します。

> sam deploy --guided

Configuring SAM deploy
======================

    Setting default arguments for 'sam deploy'
    =========================================
    Stack Name [cw-alarm-notification-with-jst]: cw-alarm-notification-with-jst
    AWS Region [ap-northeast-1]: ap-northeast-1
    Parameter EmailAddress []: sample@example.co.jp

確認

適当に作成した CloudWatch Alarm の通知先を作成した SNSトピック(via CloudWatch) に指定して、 アラーム状態にしてみました。

img

以下のような メールが届きます。

ec2-cpu-alarm-test state is now ALARM: 
Threshold Crossed: 1 out of the last 1 datapoints [50.0 (15/09/20 09:18:00)] was greater than the threshold (70.0) (minimum 1 datapoint for OK -> ALARM transition).
**StateChangeTime(JST): 2020-09-15 18:33:54.193000+09:00**
### Details
- AlarmName: ec2-cpu-alarm-test
- AlarmDescription: ec2 cpu alarm test
- AWSAccountId: XXXXXXXXXXXX
- NewStateValue: ALARM
- NewStateReason: Threshold Crossed: 1 out of the last 1 datapoints [50.0 (15/09/20 09:18:00)] was greater than the threshold (70.0) (minimum 1 datapoint for OK -> ALARM transition).
- StateChangeTime: 2020-09-15T09:33:54.193+0000
- Region: Asia Pacific (Tokyo)
- AlarmArn: arn:aws:cloudwatch:ap-northeast-1:XXXXXXXXXXXX:alarm:ec2-cpu-alarm-test
- OldStateValue: OK
- Trigger: {'MetricName': 'CPUUtilization', 'Namespace': 'AWS/EC2', 'StatisticType': 'Statistic', 'Statistic': 'AVERAGE', 'Unit': None, 'Dimensions': [{'value': 'i-01xxxx', 'name': 'InstanceId'}], 'Period': 300, 'EvaluationPeriods': 1, 'ComparisonOperator': 'GreaterThanThreshold', 'Threshold': 70.0, 'TreatMissingData': '- TreatMissingData:                    missing', 'EvaluateLowSampleCountPercentile': ''}

通知に JST時刻 StateChangeTime(JST): 2020-09-15 18:33:54.193000+09:00 を付与できました。 (それ以外は、Alarmの情報をそのまま出力しているだけです)

※ちなみにアラームのテストは AWS CLIから強制的に行うことが可能です。以下参考ください。

おわりに

以上、CloudWatch Alarm の情報に JST時刻を付与してメール通知してみました。

CloudWatch Alarm → SNS → メール の構成の場合、受信するメールの文は 良い感じに加工してくれるみたいですね。 ( You are receiving this email because your Amazon CloudWatch Alarm ... の部分)

Lambdaを介した今回の構成では、この加工は適用されません。 なので 「オリジナルの文章を 時刻だけJSTにして他はそのままに」みたいなことはできませんでした。

オリジナルの文章ほどリッチじゃありませんが、JST時間を付与できました。

少しでもどなたかのお役に立てば幸いです。

参考

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.